package soar.test.cipher;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.security.PublicKey;
import java.security.PrivateKey;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyFactory;
import java.security.SecureRandom;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.MessageDigest;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.SecretKeySpec;

import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

public class App
{
    public static void main(String[] args)
    {
        new App().run();
    }

    private void run()
    {
        try
        {
            String split = "==================================================";
            System.out.println(split);

            test_aes();
            System.out.println(split);

            test_des();
            System.out.println(split);

            test_rsa();
            System.out.println(split);

            test_blowfish();
            System.out.println(split);

            test_base64();
            System.out.println(split);

            test_digest();
            System.out.println(split);

            test_hmac();
            System.out.println(split);

            test_compress();
            System.out.println(split);

            test_hash();
            System.out.println(split);

            test_ticket();
            System.out.println(split);
        }
        catch (Exception e)
        {
            System.err.println(String.format("fail: %s", e));
        }
    }

    private void test() throws Exception
    {
        String s = "03无效商户02";
        MessageDigest md = MessageDigest.getInstance("SHA-1");
        String e1 = expandBytesToString(md.digest(s.getBytes("GBK")));
        String e2 = expandBytesToString(md.digest(e1.getBytes()));
        System.out.println(
                String.format("s: (%s)\ne1: (%s)\ne2: (%s)", s, e1, e2));
    }

    private String plain_sample = "登快阁 黄庭坚（宋）" + "------------------"
            + "痴儿了却公家事，快阁东西倚晚晴。" + "落木千山天远大，澄江一道月分明。" + "朱弦已为佳人绝，青眼聊因美酒横。"
            + "万里归船弄长笛，此心吾与白鸥盟。";

    private static final int RSA_PKCS1_PADDING_SIZE = 11;

    private void test_aes()
    {
        try
        {
            System.out.println("AES");
            System.out.println();

            // KeyGenerator gen=KeyGenerator.getInstance("AES");
            // gen.init(128);
            // Key key=gen.generateKey();

            String plain = plain_sample;
            String passwd;
            // 16 byte = 128 bit
            passwd = "1234567890" + "123456";
            // 32 byte = 256 bit
            passwd = "1234567890" + "123456" + "1234567890" + "123456";
            SecretKeySpec key = new SecretKeySpec(passwd.getBytes(), "AES");

            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] enc_byte = cipher.doFinal(plain.getBytes());
            String enc = expandBytesToString(enc_byte);
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] dec_byte = cipher.doFinal(parseStringToBytes(enc));
            String dec = new String(dec_byte);

            // PHP mcrypt以0填充
            // String
            // enc="3BD83B29F5AF71BF79270F5DD607ACBA4E47FEFF08DF1075F7B93136C9A05C7E1395AAFF07BFFBE57A2FA1B028BAB41E45785ED392ACF087B3DD127213F203D4716EDE77074BA0B56610637A7C45D71F8D3B4D722DD38FCEAA9BB26055D9BF068A8DC6DAF46BB965291FCA83B6383032FC6E0626E59B82AAB80B4C1DAA2EA7775FC31C10B4DBA0F297F3CB22F2218EB87828E35996334E9DAC29AB6BE9D77917FD5086DD78041E54A04DF156F8C5FA3BD988107EE457CB0099254FDF4293797A193B2122C6A975B66243B19E1DFA4A11D142ADD9A930E6BD33174656A15222E36D1FBEF511D5F704136C50D46B1232A72C9994CA5A32CE8B551E09E6F462FA47";
            // Cipher cipher=Cipher.getInstance("AES/ECB/NoPadding");
            // cipher.init(Cipher.DECRYPT_MODE, key);
            // byte[] dec_byte=cipher.doFinal(CollapseFromString(enc));
            // String dec=new String(dec_byte).trim();

            FileOutputStream fs = new FileOutputStream("aes_bin_tmp.txt");
            fs.write(dec_byte);
            fs.close();
            FileWriter fw = new FileWriter("aes_str_tmp.txt");
            fw.write(dec + "\n");
            fw.close();

            System.out.println(String.format("plain: (%s)", plain));
            System.out.println(String.format("enc: (%s)", enc));
            System.out.println(String.format("dec: (%s)", dec));
            System.out.println();

            cipher.init(Cipher.ENCRYPT_MODE, key);
            String s = "123456|abcdef|291872722";
            enc_byte = cipher.doFinal(s.getBytes());
            enc = expandBytesToString(enc_byte);
            System.out.println(String.format("plain : %d (%s)", s.length(), s));
            System.out.println(
                    String.format("expand : %d (%s)", enc.length(), enc));
            enc = Base64.getEncoder().encodeToString(enc_byte);
            System.out.println(
                    String.format("base64 : %d (%s)", enc.length(), enc));
            enc_byte = Base64.getDecoder().decode(enc);
            enc = expandBytesToString(enc_byte);
            System.out.println(
                    String.format("unbase64 : %d (%s)", enc.length(), enc));
        }
        catch (Exception e)
        {
            System.out.println(String.format("AES fail: %s", e));
            return;
        }
    }

    private void test_des()
    {
        try
        {
            System.out.println("DES-triple");
            System.out.println();

            String plain = plain_sample;
            String passwd = "a!b@c#d$e%f^g&h*i(j)k-l+"; // 8 byte * 3
            // SecretKeySpec key_spec=new SecretKeySpec(passwd.getBytes(),
            // "DESede");
            // SecretKeyFactory key_factory=
            // SecretKeyFactory.getInstance("DESede");
            // SecretKey key=key_factory.generateSecret(key_spec);
            SecretKeySpec key = new SecretKeySpec(passwd.getBytes(), "DESede");
            Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");

            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] enc_byte = cipher.doFinal(plain.getBytes());
            String enc = expandBytesToString(enc_byte);

            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] dec_byte = cipher.doFinal(parseStringToBytes(enc));
            String dec = new String(dec_byte);

            System.out.println(String.format("plain: (%s)", plain));
            System.out.println(String.format("enc: (%s)", enc));
            System.out.println(String.format("dec: (%s)", dec));
        }
        catch (Exception e)
        {
            System.out.println(String.format("DES fail: %s", e.toString()));
            return;
        }
    }

    private class GenerateKey
    {
        PublicKey pubkey;
        PrivateKey prikey;
    }

    private void test_rsa()
    {
        String split = "--------------------------------------------------";

        System.out.println("RSA");
        System.out.println();

        try
        {

            Date begin;
            Date end;

            begin = new Date();
            String plain = plain_sample;
            GenerateKey genkey = generate_rsa_key();
            PublicKey pubkey = genkey.pubkey;
            PrivateKey prikey = genkey.prikey;
            end = new Date();
            System.out.println(String.format("Generate key: %d ms",
                    end.getTime() - begin.getTime()));

            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

            begin = new Date();
            cipher.init(Cipher.ENCRYPT_MODE, pubkey);
            byte[] enc_byte = rsa_encrypt(cipher, plain.getBytes());
            String enc = expandBytesToString(enc_byte);
            end = new Date();
            System.out.println(String.format("Encrypt: %d ms",
                    end.getTime() - begin.getTime()));

            begin = new Date();
            cipher.init(Cipher.DECRYPT_MODE, prikey);
            byte[] dec_byte = rsa_decrypt(cipher, parseStringToBytes(enc));
            String dec = new String(dec_byte);
            end = new Date();
            System.out.println(String.format("Decrypt: %d ms",
                    end.getTime() - begin.getTime()));

            System.out.println(String.format("plain: (%s)", plain));
            System.out.println(String.format("enc: (%s)", enc));
            System.out.println(String.format("dec: (%s)", dec));

            System.out.println(split);

            String private_key_base64;
            // PEM 错误
            private_key_base64 = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMqj/lb+Vt2QVHU2WxRrIhvTq8YCnyBySeQmoLjC7iIOtEsIfuv9PLGLavY1ZfyGy/RbeFJhp6CRh7x7MJE6hZ53gQzEpM/VsNEJO06cM4gyAMyDRF//UtEZuR8j4EX9jE1UaSoC7C2XvIxMN94rzshOq53Yn6XofASjpkUYl+udAgMBAAECgYB72zqk/3YmnIYS0nNfCpBhg8I1F/sdAN4LFlUcOQEGTgiC6CFzqmgDeNyauk6ox9c3dwUXDVtAuFLHsGAiFCwAdaoZxCJlzcktueCVm1tqBJwDOiAfIVERw8R+kbbSLDFqoFZrKlFV5fwsaNFpJnq0Nnz7006sKrYVnb8QyKmTwQJBAPOaxn3se7r59yw7FI0lOVjQKuQDBKFMvhJIF68ztC9ln03JiqNn5kYPMRGWSKgGwwjPUZB079znhbrkzEd1Iw0CQQDU852u2Ia4E8wB6aasIzQt1k9VApPY4YfbCtYq+Mm+ovo4PFl/LIsEBwFanMP4izt988Aj+nncGBhMUKtWtAbRAkAgeOE8p+lWYzuvibCToAKk9BrDiiNz3Ob5SOLfV+UM7F7v3427en4XmK7RyVamD0CwS/JzbyMKL1KENzuDyvmBAkBYvUQiTlylbKqAyYsJBIqD52Cbvj06DZF9d+CTe6Umaef3cz6gPkGX5gtsZRNNCUg7Tk3tMD2DuA64Do23zcFhAkEAix6xcJfT3SajQOrHfsZT5qHWm340Eg+eMJw5jEVBnSug4HdnWoYgc9w/yFXISzC0hic1OWa62EpRVbq7V/Ckcg==";
            // PKCS8
            private_key_base64 = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALoPW9ghfAYiKvQn3eptdZdehbK5KO3vmM4NKHVGvKo50kOKTEb4KRRlr3oI1L8sUyUjaOZGN6zFWr7rV1YE5/zLQ2qCTFEBtlTdM2e4lTEqDQjkRnbHlkle8EfjemDjsT3IbISknkkCNuVQtV0lqvHGFKneepuBvcv3w9UR1RkXAgMBAAECgYAlBUlRB9iVn9bMHeKZ8o2jQLt5hD4Qmcvds+CI9To05Kxho/LpbBdAZ7synO832qnwicslhnMtfVdBBbKq+u7tlpe47s2emuCQprDGNgkZ2IjCGOdJxPp/NUqBDyJFSs79gxvv7J3g2fCOJGuR7sGooD3ZxGxmGqEf985WyxeYgQJBAO3i2UFNFYRg1fepRgp5ibatdo5oOPjuR6V0zcMSpiU//Xgs8vpFBG+TyrGI1hgUHEhtO38Kgx2mkNR1basDtj0CQQDIOkFXe/wvw9YTr5JP425AH0jNC7wnJtM9iDsdg0jHd68nOnvS6X6HnZr+yXVCmnVV8u9tc6huABfIP1qm7ZXjAkEAoazEVRJLpm2d3jVIagQSKXqZEc4Zsm1Xekb5NTQugTCNVyjNdnSDwEVkw+YpA7JfSoYaoowFcphq5Epu34SlTQJBAJjQbUI5rFVAH8jSfNZf+6sQtV1ZEXbIGPyjVorFwewKmjFUX2CvOBSE4uCGHOqCO/8m9DkSxn99rqj0QVmZhoUCQFh2f1T+mCPjUbALsrqjZyDzzlx3QVrXSOS4vpEemEO950ClbpunGc/XfAITr5fC4CjC+0l3RJV7H88yv544oVU=";
            byte[] private_key_byte = Base64.getDecoder()
                    .decode(private_key_base64);
            System.out.println("private key base64 decode succeed");
            PKCS8EncodedKeySpec key_spec = new PKCS8EncodedKeySpec(
                    private_key_byte);
            KeyFactory key_factory = KeyFactory.getInstance("RSA");
            PrivateKey pri_key = key_factory.generatePrivate(key_spec);
            System.out.println("load private key succeed");
        }
        catch (Exception e)
        {
            System.out.println(String.format("RSA fail: %s", e.toString()));
            return;
        }
    }

    private void test_blowfish()
    {
        try
        {
            System.out.println("Blowfish");
            System.out.println();

            String passwd;
            // 8 byte = 64 bit
            passwd = "12345678";
            SecretKeySpec key = new SecretKeySpec(passwd.getBytes(),
                    "Blowfish");

            Cipher cipher = Cipher.getInstance("Blowfish/ECB/PKCS5Padding");

            Random random = new Random();
            for (int i = 0; i < 2; i++)
            {
                long id = 198794535432L + i;
                int salt = random.nextInt();
                byte[] salt_bytes = convertIntToBytes(salt);
                byte[] id_bytes = convertLongToBytes(id);
                byte[] plain_bytes = new byte[salt_bytes.length
                        + id_bytes.length];
                int offset = 0;
                System.arraycopy(salt_bytes, 0, plain_bytes, offset,
                        salt_bytes.length);
                offset += salt_bytes.length;
                System.arraycopy(id_bytes, 0, plain_bytes, offset,
                        id_bytes.length);
                String plain = String.format("%d %d", salt, id);
                System.out.println(String.format("plain: (%s)", plain));

                cipher.init(Cipher.ENCRYPT_MODE, key);
                byte[] enc_byte = cipher.doFinal(plain_bytes);
                String enc = expandBytesToString(enc_byte);
                System.out.println(
                        String.format("enc: %d (%s)", enc.length(), enc));
                enc = Base64.getEncoder().encodeToString(enc_byte);
                System.out.println(String.format("enc base64 : %d (%s)",
                        enc.length(), enc));

                cipher.init(Cipher.DECRYPT_MODE, key);
                enc_byte = Base64.getDecoder().decode(enc);
                byte[] dec_bytes = cipher.doFinal(enc_byte);
                System.out.println(String.format("dec: %d", dec_bytes.length));
                offset = 0;
                System.arraycopy(dec_bytes, offset, salt_bytes, 0,
                        salt_bytes.length);
                offset += salt_bytes.length;
                System.arraycopy(dec_bytes, offset, id_bytes, 0,
                        id_bytes.length);
                salt = convertBytesToInt(salt_bytes);
                id = convertBytesToLong(id_bytes);
                System.out.println(String.format("dec: (%d %d)", salt, id));

                System.out.println();
            }
        }
        catch (Exception e)
        {
            System.out.println(String.format("Blowfish fail: %s", e));
            return;
        }
    }

    private GenerateKey generate_rsa_key() throws Exception
    {
        final int KEY_SIZE = 1024;
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(KEY_SIZE, new SecureRandom());
        KeyPair pair = generator.generateKeyPair();

        KeyFactory keyfactory = KeyFactory.getInstance("RSA");

        /*----- 公钥 -----*/
        // PublicKey.getEncoded() -> byte[]
        PublicKey pubkey = pair.getPublic();
        byte[] pubkey_byte = pubkey.getEncoded();
        // System.out.println(String.format(
        // "pubkey(%d): %s",
        // pubkey_byte.length, Arrays.toString(pubkey_byte)
        // ));
        String pubkey_enc = Base64.getEncoder().encodeToString(pubkey_byte);
        pubkey_enc = pubkey_enc.replaceAll("\n", "");

        String pubkey_filename = "public";
        FileWriter pubkey_fw = new FileWriter(pubkey_filename);
        pubkey_fw.write("ssh-rsa ");
        pubkey_fw.write(pubkey_enc);
        pubkey_fw.write(" arron@zhouyunfei.local\n");
        pubkey_fw.close();

        // byte[] -> X509EncodedKeySpec.generatePublic -> PublicKey
        X509EncodedKeySpec pubkey_x509 = new X509EncodedKeySpec(pubkey_byte);
        pubkey = keyfactory.generatePublic(pubkey_x509);

        /*----- 私钥 -----*/
        // Private.getEncoded() -> byte[]
        PrivateKey prikey = pair.getPrivate();
        byte[] prikey_byte = prikey.getEncoded();
        // System.out.println(String.format(
        // "prikey(%d): %s",
        // prikey_byte.length, Arrays.toString(prikey_byte)
        // ));
        // String prikey_password="a!b@c#d$"; // ASCII
        // byte[] prikey_enc=encrypt_private_key(prikey_password,
        // prikey_byte);
        String prikey_enc = Base64.getEncoder().encodeToString(prikey_byte);

        String prikey_filename = "private";
        FileWriter prikey_fw = new FileWriter(prikey_filename);
        prikey_fw.write("-----BEGIN RSA PRIVATE KEY-----\n");
        prikey_fw.write(prikey_enc);
        prikey_fw.write("\n-----END RSA PRIVATE KEY-----\n");
        prikey_fw.close();

        // byte[] -> PKCS8EncodedKeySpec.generatePrivate -> PrivateKey
        PKCS8EncodedKeySpec prikey_pkcs8 = new PKCS8EncodedKeySpec(prikey_byte);
        prikey = keyfactory.generatePrivate(prikey_pkcs8);

        GenerateKey genkey = new GenerateKey();
        genkey.pubkey = pubkey;
        genkey.prikey = prikey;
        return genkey;
    }

    private byte[] encrypt_private_key(String password, byte[] prikey_byte)
            throws Exception
    {
        int MD5_ITERATIONS = 1000;
        byte[] salt = new byte[8];
        SecureRandom random = new SecureRandom();
        random.nextBytes(salt);

        PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
        SecretKeyFactory keyFactory = SecretKeyFactory
                .getInstance("PBEWithSHA1AndDESede");
        SecretKey key = keyFactory.generateSecret(keySpec);
        PBEParameterSpec paramSpec = new PBEParameterSpec(salt, MD5_ITERATIONS);
        Cipher cipher = Cipher.getInstance("PBEWithSHA1AndDESede");
        cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
        byte[] enc = cipher.doFinal(prikey_byte);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(salt);
        baos.write(enc);
        return baos.toByteArray();
    }

    private byte[] rsa_encrypt(Cipher cipher, byte[] plain_byte)
            throws Exception
    {
        // int block_size=cipher.getBlockSize();
        int block_size = cipher.getOutputSize(0) - RSA_PKCS1_PADDING_SIZE;
        // System.out.println("block_size: "+block_size);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int offset = 0;
        while (offset < plain_byte.length)
        {
            int size = plain_byte.length - offset < block_size
                    ? plain_byte.length - offset
                    : block_size;
            baos.write(cipher.doFinal(plain_byte, offset, size));
            offset += size;
        }
        return baos.toByteArray();
    }

    private byte[] rsa_decrypt(Cipher cipher, byte[] enc_byte) throws Exception
    {
        int output_size = cipher.getOutputSize(0);
        if (enc_byte.length % output_size != 0)
            return new byte[] { 0 };

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int offset = 0;
        while (offset < enc_byte.length)
        {
            baos.write(cipher.doFinal(enc_byte, offset, output_size));
            offset += output_size;
        }
        return baos.toByteArray();
    }

    private void test_base64()
    {
        try
        {
            System.out.println("Base64");
            System.out.println();

            String plain = plain_sample;
            byte[] enc_b = Base64.getEncoder().encode(plain.getBytes());
            String enc = Base64.getEncoder().encodeToString(plain.getBytes());
            String dec = new String(Base64.getDecoder().decode(enc));
            System.out.println(String.format("plain: (%s)", plain));
            System.out.println(String.format("enc_b: %d (%s)", enc_b.length,
                    new String(enc_b)));
            System.out
                    .println(String.format("enc: %d (%s)", enc.length(), enc));
            System.out.println(String.format("dec: (%s)", dec));
            System.out.println();

            plain = "<request><merchant_id>46</merchant_id><order_id>33</order_id><amount>3</amount><description>hehe</description></request>";
            enc = Base64.getEncoder().encodeToString(plain.getBytes());
            System.out.println(
                    String.format("enc   : %d (%s)", enc.length(), enc));
            enc = Base64.getUrlEncoder().encodeToString(plain.getBytes());
            System.out.println(
                    String.format("urlenc: %d (%s)", enc.length(), enc));
        }
        catch (Exception e)
        {
            System.out.println(String.format("Base64 fail: %s", e));
            return;
        }
    }

    private void test_digest()
    {
        try
        {
            System.out.println("digest");
            System.out.println();

            String plain = plain_sample;
            System.out.println(String.format("plain: (%s)", plain));

            MessageDigest md;
            byte[] b = plain.getBytes();

            md = MessageDigest.getInstance("MD5");
            System.out.println(String.format(
                    "MessageDigest: Algorithm(%s) DigestLength(%d)",
                    md.getAlgorithm(), md.getDigestLength()));
            md.update(b);
            String md5 = expandBytesToString(md.digest());
            System.out.println(String.format("md5: (%s)", md5));
            md.reset();
            md5 = expandBytesToString(md.digest(b));
            System.out.println(String.format("md5: (%s)", md5));

            System.out.println();

            md = MessageDigest.getInstance("SHA-1");
            System.out.println(String.format(
                    "MessageDigest: Algorithm(%s) DigestLength(%d)",
                    md.getAlgorithm(), md.getDigestLength()));
            String sha1 = expandBytesToString(md.digest(b));
            System.out.println(String.format("sha1: (%s)", sha1));

            System.out.println();

            md = MessageDigest.getInstance("SHA-256");
            System.out.println(String.format(
                    "MessageDigest: Algorithm(%s) DigestLength(%d)",
                    md.getAlgorithm(), md.getDigestLength()));
            String sha256 = expandBytesToString(md.digest(b));
            System.out.println(String.format("sha256: (%s)", sha256));

            System.out.println();

            md = MessageDigest.getInstance("SHA-512");
            System.out.println(String.format(
                    "MessageDigest: Algorithm(%s) DigestLength(%d)",
                    md.getAlgorithm(), md.getDigestLength()));
            String sha512 = expandBytesToString(md.digest(b));
            System.out.println(String.format("sha512: (%s)", sha512));
            md = MessageDigest.getInstance("SHA-512");
            md.update((plain + "xxx").getBytes());
            System.out.println(String.format("update(s1+s2): (%s)",
                    expandBytesToString(md.digest())));
            md = MessageDigest.getInstance("SHA-512");
            md.update(plain.getBytes());
            md.update("xxx".getBytes());
            System.out.println(String.format("update(s1) update(s2): (%s)",
                    expandBytesToString(md.digest())));

        }
        catch (Exception e)
        {
            System.out.println(String.format("digest fail: %s", e));
            return;
        }
    }

    private void test_hmac() throws Exception
    {
        System.out.println("hmac");
        System.out.println();

        String plain = plain_sample;
        String key = "一入江湖岁月催";

        String algorithm = "HmacSHA1";
        Mac mac = Mac.getInstance(algorithm);
        SecretKey secret = new SecretKeySpec(key.getBytes(), algorithm);
        mac.init(secret);
        byte[] checksum_b = mac.doFinal(plain.getBytes());
        String checksum = expandBytesToString(checksum_b);
        System.out.println(String.format("%s: (%s)", algorithm, checksum));
    }

    private void test_compress()
    {
        try
        {
            System.out.println("compress");
            System.out.println();

            String text1 = plain_sample;
            String text2 = text1 + "水调歌头 -- " + "高亨 -- " + "掌上千秋史，胸中百万兵，"
                    + "眼底六州风雨，笔下有雷声。" + "唤醒蛰龙飞起，扫灭魔焰魅火，挥剑斩长鲸。" + "春满人间世，日照大旗红。"
                    + "    " + "抒慷慨，写鏖战，记长征。" + "天章云锦，织出革命之豪情。"
                    + "细检诗坛李杜，词苑苏辛佳什，未有此奇雄。" + "携卷登高唱，流韵壮东风。";
            String text3 = text2 + "岳阳楼记 -- "
                    + "庆历四年春，滕子京谪守巴陵郡。越明年，政通人和，百废具兴。乃重修岳阳楼，增其旧制，刻唐贤今人诗赋于其上。属予作文以记之。"
                    + "    "
                    + "予观夫巴陵胜状，在洞庭一湖。衔远山，吞长江，浩浩汤汤，横无际涯；朝晖夕阴，气象万千。此则岳阳楼之大观也。前人之述备矣。然则北通巫峡，南极潇湘，迁客骚人，多会于此，览物之情，得无异乎？"
                    + "    "
                    + "若夫霪雨霏霏，连月不开，阴风怒号，浊浪排空；日星隐耀，山岳潜形；商旅不行，樯倾楫摧；薄暮冥冥，虎啸猿啼。登斯楼也，则有去国怀乡，忧谗畏讥，满目萧然，感极而悲者矣。"
                    + "    "
                    + "至若春和景明，波澜不惊，上下天光，一碧万顷；沙鸥翔集，锦鳞游泳；岸芷汀兰，郁郁青青。而或长烟一空，皓月千里，浮光跃金，静影沉璧，渔歌互答，此乐何极！登斯楼也，则有心旷神怡，宠辱偕忘，把酒临风，其喜洋洋者矣。"
                    + "    "
                    + "嗟夫！予尝求古仁人之心，或异二者之为，何哉？不以物喜，不以己悲；居庙堂之高则忧其民；处江湖之远则忧其君。是进亦忧，退亦忧。然则何时而乐耶？其必曰“先天下之忧而忧，后天下之乐而乐”乎。噫！微斯人，吾谁与归？"
                    + "    " + "时六年九月十五日。 ";
            String text4 = text3 + "为人民服务 -- " + "一九四四年九月八日 -- " + "毛泽东 -- "
                    + "我们的共产党和共产党所领导的八路军、新四军，是革命的队伍。我们这个队伍完全是为着解放人民的，是彻底地为人民的利益工作的。张思德同志就是我们这个队伍中的一个同志。"
                    + "人总是要死的，但死的意义有不同。中国古时候有个文学家叫做司马迁的说过：人固有一死，或重于泰山，或轻于鸿毛。为人民利益而死，就比泰山还重；替法西斯卖力，替剥削人民和压迫人民的人去死，就比鸿毛还轻。张思德同志是为人民利益而死的，他的死是比泰山还要重的。"
                    + "因为我们是为人民服务的，所以，我们如果有缺点，就不怕别人批评指出。不管是什么人，谁向我们指出都行。只要你说得对，我们就改正。你说的办法对人民有好处，我们就照你的办。“精兵简政”这一条意见，就是党外人士李鼎铭先生提出来的；他提得好，对人民有好处，我们就采用了。只要我们为人民的利益坚持好的，为人民的利益改正错的，我们这个队伍就一定会兴旺起来。"
                    + "我们都是来自五湖四海，为了一个共同的革命目标，走到一起来了。我们还要和全国大多数人民走这一条路。我们今天已经领导着有九千一百万人口的根据地，但是还不够，还要更大些，才能取得全民族的解放。我们的同志在困难的时候，要看到成绩，要看到光明，要提高我们的勇气。中国人民正在受难，我们有责任解救他们，我们要努力奋斗。要奋斗就会有牺牲，死人的事是经常发生的。但是我们想到人民的利益，想到大多数人民的痛苦，我们为人民而死，就是死得其所。不过，我们应当尽量地减少那些不必要的牺牲。我们的干部要关心每一个战士，一切革命队伍的人都要互相关心，互相爱护，互相帮助。"
                    + "今后我们的队伍里，不管死了谁，不管是炊事员，是战士，只要他是做过一些有益的工作的，我们都要给他送葬，开追悼会。这要成为一个制度。这个方法也要介绍到老百姓那里去。村上的人死了，开个追悼会。用这样的方法，寄托我们的哀思，使整个人民团结起来。";

            // 压缩
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            GZIPOutputStream gzip = new GZIPOutputStream(out);
            gzip.write(text1.getBytes());
            gzip.close();
            System.out.println(
                    String.format("text1: (%d)", text1.getBytes().length));
            System.out.println(
                    String.format("gzip1: (%d)", out.toByteArray().length));
            System.out.println();

            out = new ByteArrayOutputStream();
            gzip = new GZIPOutputStream(out);
            gzip.write(text2.getBytes());
            gzip.close();
            System.out.println(
                    String.format("text2: (%d)", text2.getBytes().length));
            System.out.println(
                    String.format("gzip2: (%d)", out.toByteArray().length));
            System.out.println();

            out = new ByteArrayOutputStream();
            gzip = new GZIPOutputStream(out);
            gzip.write(text3.getBytes());
            gzip.close();
            System.out.println(
                    String.format("text3: (%d)", text3.getBytes().length));
            System.out.println(
                    String.format("gzip3: (%d)", out.toByteArray().length));
            System.out.println();

            out = new ByteArrayOutputStream();
            gzip = new GZIPOutputStream(out);
            gzip.write(text4.getBytes());
            gzip.close();
            System.out.println(
                    String.format("text4: (%d)", text4.getBytes().length));
            System.out.println(
                    String.format("gzip4: (%d)", out.toByteArray().length));
            System.out.println();

            // 解压
            ByteArrayInputStream in = new ByteArrayInputStream(
                    out.toByteArray());
            GZIPInputStream gunzip = new GZIPInputStream(in);
            final int EXPAND_SIZE = 1024;
            ByteBuffer bb = ByteBuffer.allocate(EXPAND_SIZE);
            // byte[] text_b = new byte[0];
            byte[] buf = new byte[1024]; // 1k或10k影响不大
            int count;
            // int offset = 0;
            while ((count = gunzip.read(buf)) != -1)
            {
                System.out.println(String.format("count(%d)", count));
                if (bb.remaining() < count)
                {
                    System.out.println(String.format("remaining(%d) + %d = %d",
                            bb.remaining(), EXPAND_SIZE,
                            bb.remaining() + EXPAND_SIZE));
                    // bb.flip();
                    // byte[] tmp = new byte[bb.remaining()];
                    // bb.get(tmp);
                    // bb = ByteBuffer.allocate(bb.capacity() + EXPAND_SIZE);
                    // bb.put(tmp);
                    ByteBuffer bbtmp = bb;
                    bb = ByteBuffer.allocate(bbtmp.capacity() + EXPAND_SIZE);
                    bbtmp.flip();
                    bb.put(bbtmp);
                }
                System.out
                        .print(String.format("remaining(%d)", bb.remaining()));
                bb.put(buf, 0, count);
                System.out.println(String.format(" - count(%d) = %d", count,
                        bb.remaining()));
                System.out.println();

                // byte[] tmp = new byte[offset + count];
                // System.arraycopy(text_b, 0, tmp, 0, offset);
                // System.arraycopy(buf, 0, tmp, offset, count);
                // text_b = tmp;
                // offset += count;
            }
            // System.out.println(String.format("gunzip text: (%d) (%s)",
            // text_b.length, new String(text_b)));
            bb.flip();
            System.out.println(String.format("gunzip text: (%d) (%s)",
                    bb.remaining(), Charset.defaultCharset().newDecoder()
                            .decode(bb).toString()));
            // byte[] bs = new byte[bb.remaining()];
            // bb.get(bs);
            // System.out.println(String.format("gunzip text: (%d) (%s)",
            // bs.length, new String(bs)));
        }
        catch (Exception e)
        {
            System.out.println(String.format("compress fail: %s", e));
            return;
        }
    }

    private void test_hash()
    {
        System.out.println("String.hashCode");
        System.out.println();

        final int MOD = 100;
        String[] ids = { "arron", "arron1", "arron2", "29163589", "30700644",
                "34347472", "26683028", "33143139", "35756758", "26172700",
                "29657317", "29620681", "31154077" };
        for (String s : ids)
        {
            int hash_int = s.hashCode();
            long hash_long = s.hashCode() & 0xFFFFFFFFL;
            System.out.println(String.format(
                    "%s: hashCode(%d), long(%d) %% %d = %d", s, hash_int,
                    hash_long, MOD, (s.hashCode() & 0xFFFFFFFFL) % MOD));
        }

        int[] ints = { 29163589, 30700644, 34347472, 26683028, 33143139,
                35756758, 26172700, 29657317, 29620681, 31154077 };
        for (int i : ints)
        {
            int hash_int = Integer.valueOf(i).hashCode();
            int hash_string = String.valueOf(i).hashCode();
            System.out.println(String.format(
                    "%d: Integer.hashCode(%d) String.hashCode(%d)", i, hash_int,
                    hash_string));
        }

        String[] arr = new String[] { "abc", "999", "123", "ABC" };
        String s = Stream.of(arr).collect(Collectors.joining(" "));
        System.out.println(
                String.format("String(%s): hashCode(%d)", s, s.hashCode()));
        s = Stream.of(arr).sorted().collect(Collectors.joining(" "));
        System.out.println(
                String.format("String(%s): hashCode(%d)", s, s.hashCode()));
        s = "abc 999 123 ABC";
        System.out.println(
                String.format("String(%s): hashCode(%d)", s, s.hashCode()));
        System.out.println();
    }

    private void test_ticket() throws Exception
    {
        System.out.println("ticket");
        System.out.println();

        String ticket_cal;

        String key = "ANS39ML8AO7";
        int uid = 49439607;
        String nickname;
        String password = "f6e4c58fb531f8a449441b62eb30dc6c1e2ff5c33a4ca93782113ad85335be3b";

        nickname = "虎扑JR0665429178";
        ticket_cal = generate_ticket(key, uid, nickname, password);
        String ticket = "f99c57b7d16623ab10fb66762398a12f";
        System.out.println(
                String.format("nickname:\t%s\nticket_cal:\t%s\nticket:\t\t%s",
                        nickname, ticket_cal, ticket));
        System.out.println();

        nickname = "EdwardSong";
        ticket_cal = generate_ticket(key, uid, nickname, password);
        System.out.println(String.format("nickname:\t%s\nticket_cal:\t%s",
                nickname, ticket_cal));
        System.out.println();
    }

    private String generate_ticket(String key, int uid, String nickname,
            String password) throws Exception
    {
        return md5(new StringBuffer().append(key).append(String.valueOf(uid))
                .append(Base64.getEncoder().encodeToString(nickname.getBytes()))
                .append(md5(String.valueOf(uid))).append(password
                        .substring(password.length() - 4, password.length()))
                .toString());
    }

    private String md5(String s) throws Exception
    {
        // MessageDigest md = MessageDigest.getInstance("MD5");
        // md.update(s.getBytes());
        return expandBytesToString(
                MessageDigest.getInstance("MD5").digest(s.getBytes()));
    }

    private int toHash(String key)
    {
        int arraySize = 11113; // 数组大小一般取质数
        int hashCode = 0;
        for (int i = 0; i < key.length(); i++)
        { // 从字符串的左边开始计算
            int letterValue = key.charAt(i) - 96;// 将获取到的字符串转换成数字，比如a的码值是97，则97-96=1
                                                 // 就代表a的值，同理b=2；
            hashCode = ((hashCode << 5) + letterValue) % arraySize;// 防止编码溢出，对每步结果都进行取模运算
        }
        return hashCode;
    }

    private String expandBytesToString(byte[] b_arr)
    {
        StringBuilder builder = new StringBuilder();
        for (byte b : b_arr)
        {
            builder.append(String.format("%02x", b));
        }
        return builder.toString();
    }

    private byte[] parseStringToBytes(String s)
    {
        int bsize = s.length() / 2;
        byte[] bytes = new byte[bsize];
        for (int i = 0; i < bsize; i++)
        {
            bytes[i] = (byte) Integer.parseInt(s.substring(i * 2, (i + 1) * 2),
                    16);
        }
        return bytes;
    }

    private static final int BITS_OF_BYTE = 8;
    private static final int BITS_OF_INT = 32;
    private static final int BITS_OF_LONG = 64;

    private byte[] convertIntToBytes(int int_value)
    {
        // byte[] bytes = new byte[BITS_OF_INT / BITS_OF_BYTE];
        // for (int i = 0; i < bytes.length; i++)
        // {
        // bytes[bytes.length - i - 1] = (byte) (int_value & 0xff);
        // int_value >>= BITS_OF_BYTE;
        // }
        // return bytes;
        return convert_number_to_bytes(int_value, BITS_OF_INT / BITS_OF_BYTE);
    }

    private int convertBytesToInt(byte[] bytes)
    {
        // int int_value = 0;
        // for (int i = 0; i < bytes.length; i++)
        // {
        // int_value <<= BITS_OF_BYTE;
        // int_value |= bytes[i] & 0xff;
        // }
        // return int_value;
        return (int) convert_bytes_number(bytes);

    }

    private byte[] convertLongToBytes(long long_value)
    {
        // byte[] bytes = new byte[BITS_OF_LONG / BITS_OF_BYTE];
        // for (int i = 0; i < bytes.length; i++)
        // {
        // bytes[bytes.length - i - 1] = (byte) (long_value & 0xff);
        // long_value >>= BITS_OF_BYTE;
        // }
        // return bytes;
        return convert_number_to_bytes(long_value, BITS_OF_LONG / BITS_OF_BYTE);
    }

    private long convertBytesToLong(byte[] bytes)
    {
        // long long_value = 0;
        // for (int i = 0; i < bytes.length; i++)
        // {
        // long_value <<= BITS_OF_BYTE;
        // long_value |= bytes[i] & 0xff;
        // }
        // return long_value;
        return convert_bytes_number(bytes);
    }

    private byte[] convert_number_to_bytes(long value, int length)
    {
        byte[] bytes = new byte[length];
        for (int i = 0; i < bytes.length; i++)
        {
            bytes[bytes.length - i - 1] = (byte) (value & 0xff);
            value >>= BITS_OF_BYTE;
        }
        return bytes;
    }

    private long convert_bytes_number(byte[] bytes)
    {
        long value = 0;
        for (int i = 0; i < bytes.length; i++)
        {
            value <<= BITS_OF_BYTE;
            value |= bytes[i] & 0xff;
        }
        return value;
    }
}
